package com.alex.springbootdemo.lock;

import com.alex.springbootdemo.common.ResultCode;
import com.alex.springbootdemo.exception.BusinessException;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Service;

import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;

/**
 *
 */
@Service
@AllArgsConstructor
@Slf4j
public class RedisLockService implements LockService {


    private final RedissonClient redissonClient;


    @Override
    public <T> T executeWithLock(String name, Callable<T> func) {
        try {
            return internalLockExecute(name, func);
        } catch (InterruptedException e) {
            throw new LockException(e);
        }
    }

    private <T> T internalLockExecute(String name, Callable<T> func) throws InterruptedException {
        RLock lock = redissonClient.getLock(name);
        log.trace("Acquiring lock {}", name);
        lock.lock();
        try {
            log.trace("Acquired lock {}", name);
            return func.call();
        } catch (Exception e) {
            if (e instanceof RuntimeException) {
                throw (RuntimeException) e;
            } else {
                throw new LockException(e);
            }
        } finally {
            lock.unlock();
            log.trace("Unlocked lock {}", name);
        }
    }


    @Override
    public <T> T executeWithLockThrows(String key, int waitTime, TimeUnit unit, SupplierThrow<T> supplier) throws Throwable {
        RLock lock = redissonClient.getLock(key);
        log.trace("Acquiring lock {}", key);
        boolean lockSuccess = lock.tryLock(waitTime, unit);
        if (!lockSuccess) {
            throw new BusinessException(ResultCode.LOCK_LIMIT);
        }
        try {
            return supplier.get();
        } finally {
            lock.unlock();
        }
    }
}
